Skip to content

docs: Add plotly.js v4 migration guide - #455

Open
camdecoster wants to merge 12 commits into
masterfrom
cam/add-v4-migration-guide
Open

docs: Add plotly.js v4 migration guide#455
camdecoster wants to merge 12 commits into
masterfrom
cam/add-v4-migration-guide

Conversation

@camdecoster

@camdecoster camdecoster commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Description

Creates a new Guides section, moves the v3 migration guide, and ddds the plotly.js v4 migration guide.

Closes plotly/plotly.js#7831.

Changes

  • Creates Guides section
  • Moves/renames v3 migration guide
  • Adds v4 migration guide

Testing

  • Be on this branch
  • Build the docs
  • Click around the Guides and make sure everything looks okay

Notes

  • The v3 guide was in the Fundamentals section, which doesn't really fit
  • I added a redirect for the old v3 guide link
  • The styles aren't great, but fixing that will be part of a different project

@camdecoster camdecoster changed the title docs: Add v4 migration guide docs: Add plotly.js v4 migration guide Jul 17, 2026
Comment on lines +112 to +113

// After — falls back to the attribute default

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// After — falls back to the attribute default

Comment on lines +504 to +515
`marker.color` on a scattermap trace is now applied to the icon by
setting `icon-color` (plus a small `icon-halo` so the colored edges
read cleanly). In v3 the property was set on the layer but the
underlying icons weren't SDF sprites, so the color had no visible
effect — coloring was effectively a no-op.

In v4 the Maki icons are loaded with `{sdf: true}` and tinted with
`marker.color`. Figures relying on the icon retaining its original
artwork color will now render in the trace color. If you need the
original tint, pick a Maki icon already in that color and set
`marker.color: 'white'` (which becomes the identity tint for SDF
sprites).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too much detail

Suggested change
`marker.color` on a scattermap trace is now applied to the icon by
setting `icon-color` (plus a small `icon-halo` so the colored edges
read cleanly). In v3 the property was set on the layer but the
underlying icons weren't SDF sprites, so the color had no visible
effect — coloring was effectively a no-op.
In v4 the Maki icons are loaded with `{sdf: true}` and tinted with
`marker.color`. Figures relying on the icon retaining its original
artwork color will now render in the trace color. If you need the
original tint, pick a Maki icon already in that color and set
`marker.color: 'white'` (which becomes the identity tint for SDF
sprites).
`marker.color` on a scattermap trace is now applied to the icon by
setting `icon-color`.

@ndrezn ndrezn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest adding some grouping by topic, like for example. Right now it's a just a long, ungrouped list so topics come and go back and forth.

Maybe something like:

  1. Environment & tooling

Setup-level changes that hit before you touch a figure.

  • Minimum Node.js version
  • TypeScript types
  1. Removed features

Things that are simply gone and need code deleted/renamed.

  • Chart Studio APIs removed
  • Mapbox traces and subplots removed (maybe could go in maps section? but it's top item).
  1. Dependency changes

Third-party library swaps whose output shifts even with identical input — the
"your figure looks slightly different and you didn't change anything" bucket.

  • Color library swap (tinycolor2 → color)
  • Country name lookup (country-regex → country-iso-search)
  • Sankey layout algorithm update
  1. Maps & geo

By far the biggest cluster — all the MapLibre/geo framing behavior.

  • Scattermap icon defaults
  • Map subplots auto-frame to data
  • Geo subplots auto-frame by default
  • Geo fitbounds framing for antimeridian features
  • Geo subplot zoom limits
  1. Cartesian & shapes

The remaining rendering-behavior changes that aren't map-related.

  • Shape legend marker outlines honor line.dash
  • Overlaying axis tickmode defaults to 'sync'

}
```

### Built-in style values

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this previously covered when we introduced the maplibre traces? Could we reference an existing doc in the "dropping map box" section?

sanitized exact lookup against a curated alias table, which is stricter
about ambiguous input but accepts many more legitimate forms.

### Names that resolved before but no longer do

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This title isn't specific enough, it sounds like a Kafka sentence 🙃

Comment on lines +149 to +168
#### Browser compatibility

All of these are parsed by Plotly in JavaScript and normalized to legacy
`rgb(...)` / `rgba(...)` before being written to the DOM, so the browser
never sees the modern syntax — your figures render the same in any
browser that ran v3. The note below only matters if you also mirror these
strings into your own stylesheets or template literals.

Native CSS support:

| Format | First supported |
|---|---|
| `#rrggbbaa`, `#rgba` | Chrome 62 (2017), Firefox 49 (2016), Safari 9.1 (2016) |
| `rgb(r g b)`, `rgb(r g b / a)`, `hsl(h s l / a)` (space + slash-alpha) | Chrome 65 (2018), Firefox 52 (2017), Safari 12.1 (Mar 2019) |
| `hsla()` | Universal since IE9 |
| `hwb()` | Chrome 101 (Apr 2022), Firefox 96 (Jan 2022), Safari 15 (Sep 2021) |

`hwb()` is the only format here that won't parse in browsers from ~2020
or earlier. Everything else is safe in any browser shipped in the last
seven years.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this level of detail is needed in the migration guide, could it be moved to a docs page on color strings instead?

Suggested change
#### Browser compatibility
All of these are parsed by Plotly in JavaScript and normalized to legacy
`rgb(...)` / `rgba(...)` before being written to the DOM, so the browser
never sees the modern syntax — your figures render the same in any
browser that ran v3. The note below only matters if you also mirror these
strings into your own stylesheets or template literals.
Native CSS support:
| Format | First supported |
|---|---|
| `#rrggbbaa`, `#rgba` | Chrome 62 (2017), Firefox 49 (2016), Safari 9.1 (2016) |
| `rgb(r g b)`, `rgb(r g b / a)`, `hsl(h s l / a)` (space + slash-alpha) | Chrome 65 (2018), Firefox 52 (2017), Safari 12.1 (Mar 2019) |
| `hsla()` | Universal since IE9 |
| `hwb()` | Chrome 101 (Apr 2022), Firefox 96 (Jan 2022), Safari 15 (Sep 2021) |
`hwb()` is the only format here that won't parse in browsers from ~2020
or earlier. Everything else is safe in any browser shipped in the last
seven years.

`tickmode` continues to default to `'auto'` on the overlaying axis.
Sync'ing tick positions to category slots is almost never the intent.

### Opting out

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be a sub header

/ `pitch` in the layout — auto-framing steps aside and preserves the
chosen view across further data changes.

### Opting out

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be one level deeper

Comment on lines +311 to +316
### Static export

The Mapbox logo previously injected into static SVG / PNG exports of
mapbox subplots is no longer drawn — MapLibre has no equivalent
trademark requirement. Attribution provided by tile sources (Carto,
OpenStreetMap, ArcGIS) is still rendered inside the map canvas.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Static export
The Mapbox logo previously injected into static SVG / PNG exports of
mapbox subplots is no longer drawn — MapLibre has no equivalent
trademark requirement. Attribution provided by tile sources (Carto,
OpenStreetMap, ArcGIS) is still rendered inside the map canvas.

{ yaxis2: { overlaying: 'y', side: 'right', tickmode: 'auto' } }
```

### Incidental bugfix

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Incidental bugfix
### Incidental bug fix

But making the heading more specific would be better

/ `pitch` in the layout — auto-framing steps aside and preserves the
chosen view across further data changes.

### Opting out

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Opting out
### Disabling auto-framing

Comment on lines +631 to +636
### `map.bounds` is unaffected

The `map.bounds` layout attribute (which restricts panning) is a
separate concern and continues to work as in v3. Auto-framing computes
the *initial* view; `bounds` constrains *interactive* view movement.
Both can coexist.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### `map.bounds` is unaffected
The `map.bounds` layout attribute (which restricts panning) is a
separate concern and continues to work as in v3. Auto-framing computes
the *initial* view; `bounds` constrains *interactive* view movement.
Both can coexist.


### Opting out

Two ways to disable auto-framing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Two ways to disable auto-framing.
There are two ways to disable auto-framing.

Two ways to disable auto-framing.

**Set `map.fitbounds: false`** — recommended for intentional opt-out;
explicit and independent of the view values.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
explicit and independent of the view values.
**Set `map.fitbounds: false`** — disables automatic auto-framing and instead defaults to showing a map with center `(0, 0)` and zoom `1`, as in v3.

Comment on lines +599 to +600
**Set `map.center` or `map.zoom` explicitly** — any value counts as
user intent, including `zoom: 0` and `center: { lon: 0, lat: 0 }`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
**Set `map.center` or `map.zoom` explicitly** — any value counts as
user intent, including `zoom: 0` and `center: { lon: 0, lat: 0 }`.
**Set `map.center` or `map.zoom` explicitly** — passing any value for `map.zoom` or `map.center`, (including 0), uses the provided values rather than choosing zoom and center via auto-framing.

Comment on lines +607 to +621
### Which traces contribute

Each `map` subplot auto-frames independently, using only the
`scattermap` and `densitymap` traces assigned to it (via the trace's
`subplot` attribute). Each contributing trace's `lon` / `lat` arrays
feed the frame, with non-finite entries skipped. Traces belonging to
a different subplot (`map2`, `polar`, etc.) are framed by their own
subplot, not this one; traces with `visible !== true` are ignored
entirely.

**`choroplethmap` traces skip auto-framing** because they carry
`locations` / `geojson` rather than point lon/lat, so their extent
can't be included without geojson bbox handling. When a visible
`choroplethmap` sits on the subplot, the view falls back to the v3
default. Set `map.center` and `map.zoom` explicitly to frame the choropleth.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Which traces contribute
Each `map` subplot auto-frames independently, using only the
`scattermap` and `densitymap` traces assigned to it (via the trace's
`subplot` attribute). Each contributing trace's `lon` / `lat` arrays
feed the frame, with non-finite entries skipped. Traces belonging to
a different subplot (`map2`, `polar`, etc.) are framed by their own
subplot, not this one; traces with `visible !== true` are ignored
entirely.
**`choroplethmap` traces skip auto-framing** because they carry
`locations` / `geojson` rather than point lon/lat, so their extent
can't be included without geojson bbox handling. When a visible
`choroplethmap` sits on the subplot, the view falls back to the v3
default. Set `map.center` and `map.zoom` explicitly to frame the choropleth.
### Which traces are affected
`scattermap` and `densitymap` traces are affected by this change. **`choroplethmap` traces skip auto-framing** because their data contains geographic regions, whose bounding box must be computed differently. When a subplot contains a `choroplethmap` trace, auto-framing is disabled and the v3 default of center `(0, 0` and zoom `1` is used.

Comment on lines +623 to +629
### Antimeridian data

For data that straddles the antimeridian (points on both sides of ±180°),
the auto-frame picks the compact crossing range rather than the
long-way-around view. For example, `lon: [131.8855, -179]` frames as
`[131.8855, 181]` (a ~49° span across the antimeridian) instead of the
naive `[-179, 131.8855]` (~311° the wrong way).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Antimeridian data
For data that straddles the antimeridian (points on both sides of ±180°),
the auto-frame picks the compact crossing range rather than the
long-way-around view. For example, `lon: [131.8855, -179]` frames as
`[131.8855, 181]` (a ~49° span across the antimeridian) instead of the
naive `[-179, 131.8855]` (~311° the wrong way).
### Data that crosses the antimeridian
For data that straddles the antimeridian (points on both sides of ±180°, in the middle of the Pacific Ocean),
the auto-frame picks the compact crossing range rather than the
long-way-around view. For example, `lon: [131.8855, -179]` frames as
`[131.8855, 181]` (a ~49° span across the antimeridian) instead of the
naive `[-179, 131.8855]` (~311° the wrong way).

Would be nice to have a visual example here

|---|---|
| ![v3 geo world view](/all_static/images/migrating-to-v4/migration_geo_autofit_v3.png) | ![v4 geo framed on Canada+Mexico](/all_static/images/migrating-to-v4/migration_geo_autofit_v4.png) |

### How to opt out

@emilykl emilykl Jul 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### How to opt out
### Disabling auto-framing


### How to opt out

Two paths:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Two paths:
There are two ways to disable auto-framing.


Two paths:

**Explicit:** set `layout.geo.fitbounds: false` to restore the v3 default view.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
**Explicit:** set `layout.geo.fitbounds: false` to restore the v3 default view.
**Set `layout.geo.fitbounds: false`** — disables automatic auto-framing and instead defaults to showing a map with center `(0, 0)` and zoom `1`, as in v3.

Comment on lines +748 to +753
### Reset button behavior

The modebar's Reset view button restores the auto-fit view when
`fitbounds` is active, not the schema default. Prior to v4 there was no
auto-fit default, so Reset simply restored the schema default; in v4 the
initial view captured for reset is the fitted view.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this needs to be called out explicitly; this is how all of our axis autorange behavior is supposed to work.

Suggested change
### Reset button behavior
The modebar's Reset view button restores the auto-fit view when
`fitbounds` is active, not the schema default. Prior to v4 there was no
auto-fit default, so Reset simply restored the schema default; in v4 the
initial view captured for reset is the fitted view.

Comment on lines +699 to +703
| Projection kind | Projection-specific opt-out attributes |
|---|---|
| Any non-`world` scope (checked first, even if the projection is also clipped) | `center.lon`, `center.lat`, `projection.scale` |
| World scope + clipped projection (orthographic, azimuthal equal area, etc.) | `center.lon`, `center.lat`, `projection.scale`, `projection.rotation.lon`, `projection.rotation.lat` |
| World scope, non-clipped projection (miller, robinson, mercator, etc.) | `center.lon`, `center.lat`, `projection.scale`, `projection.rotation.lon` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only reason this differs across projections is because different projections support different view attributes, right?

Could we say something more general like "setting layout.center or any value under layout.projection disables auto-fitting. See for a summary of which projection attributes are supported by which geo projections.

sitemap: false
thumbnail: thumbnail/mixed.jpg
---
# What's New in Plotly.js v4

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should any of this content be added to other pages in the docs? OR is there not any existing pages any of this information could go into?


## Removed color inputs

Three color-input formats no longer work — invalid inputs fall back to the

@alexcjohnson alexcjohnson Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plotly.js colors now conform thoroughly to the CSS standard {link}. As a result...

|---|---|
| `hsv()` strings | Convert to `hsl()`, `hwb()`, hex, or `rgb()` |
| `rgb()` / `rgba()` strings with 0–1 decimal fractions | Convert `'rgb(0.5, 0.5, 0.5)'` to `'rgb(128, 128, 128)'` (or any supported string form) |
| Numeric color values (e.g. `color: 42`) | Convert to a color string |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What did color: 42 do before? Should make it clear that this does NOT apply to situations using a color map. color: [1,2,3,4,5] still works as it always did.

Comment on lines +173 to +174
are skipped. Switch to a canonical country name, an alias, or an ISO-3 / ISO-2
code.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link out to each of these sets - canonical names, full list of aliases, and standard reference lists for ISO-3 and ISO-2


| Attribute | Values | Purpose |
|---|---|---|
| `x`, `y` | numeric arrays | Arrow anchor positions. `x0` + `dx` / `y0` + `dy` shorthand also supported for regular grids. |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x0 + dx / y0 + dy implies that u and v could be 2D arrays? And then perhaps x and y could be as well? If so it's a great feature and we should state that explicitly. If not, what's the point of x0 + dx / y0 + dy?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexcjohnson The second sentence on line 83 is a hallucination. Good catch. x0 + dx cannot be used at the same time as y0 + dy.

Same as scatter, x0 + dx can only be used if y is provided, and vice-versa.

I'm not particularly attached to including them at all, they're mostly there for consistency with scatter and because they were trivial to include.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd take it out of the docs entirely then. If people find it in its current form fine, we can leave it in and it won't conflict with later additions, but it'll only be useless in rare cases. Its real power would come from allowing u and v to be 2D arrays, in which case x and y could either also be 2D or one or both could be omitted and replaced by x0 + dx etc, like you can do for heatmaps and contour maps.


### Endpoint status

The `cloud.plotly.com` endpoint is **not yet functional at the v4 release**.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awkward... make sure we remove this section as soon as it's live.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't live at the time these docs were drafted, but it's live now. This should be removed.


| Attribute | Values | Purpose |
|---|---|---|
| `x`, `y` | numeric arrays | Arrow anchor positions. `x0` + `dx` / `y0` + `dy` shorthand also supported for regular grids. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| `x`, `y` | numeric arrays | Arrow anchor positions. `x0` + `dx` / `y0` + `dy` shorthand also supported for regular grids. |
| `x`, `y` | numeric arrays | Arrow anchor positions. |

Comment on lines +195 to +201
### History

The upload flow was built out across the Plotly.js 3.7 series
(#7802 / #7852 / #7854), but shipped with `showSendToCloud` defaulting to
`false`, so unless you set the flag manually you never saw the button.
v4 (#7909) flips the default so every new figure exposes it by default —
this is a functional feature reveal, not a new API.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### History
The upload flow was built out across the Plotly.js 3.7 series
(#7802 / #7852 / #7854), but shipped with `showSendToCloud` defaulting to
`false`, so unless you set the flag manually you never saw the button.
v4 (#7909) flips the default so every new figure exposes it by default —
this is a functional feature reveal, not a new API.

Comment on lines +187 to +193
### Endpoint status

The `cloud.plotly.com` endpoint is **not yet functional at the v4 release**.
The button renders and is wired up, but clicking it does not complete an
upload until the endpoint is deployed. Once it goes live, existing figures
running v4 with the default config will start supporting uploads without
any code change on the caller side.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Endpoint status
The `cloud.plotly.com` endpoint is **not yet functional at the v4 release**.
The button renders and is wired up, but clicking it does not complete an
upload until the endpoint is deployed. Once it goes live, existing figures
running v4 with the default config will start supporting uploads without
any code change on the caller side.

Comment on lines +178 to +185

Or point the upload at a different host:

```js
Plotly.newPlot(gd, data, layout, {
plotlyServerURL: 'https://my-plotly-mirror.example.com'
});
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's not yet a practical reason to do this, so I suggest we leave this part out.

Suggested change
Or point the upload at a different host:
```js
Plotly.newPlot(gd, data, layout, {
plotlyServerURL: 'https://my-plotly-mirror.example.com'
});
```

Comment on lines +139 to +145
Plotly.js can now share the current figure to
[Plotly Cloud](https://cloud.plotly.com) from a one-click modebar button.
The button posts the figure's data and layout to `cloud.plotly.com` and
returns a shareable URL — the successor to the old Chart Studio upload
flow, but without the Chart Studio-specific config surface (`showLink`,
`sendData`, `showSources`, etc. — those are all gone; see
[Chart Studio APIs removed](#chart-studio-apis-removed)).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Plotly.js can now share the current figure to
[Plotly Cloud](https://cloud.plotly.com) from a one-click modebar button.
The button posts the figure's data and layout to `cloud.plotly.com` and
returns a shareable URL — the successor to the old Chart Studio upload
flow, but without the Chart Studio-specific config surface (`showLink`,
`sendData`, `showSources`, etc. — those are all gone; see
[Chart Studio APIs removed](#chart-studio-apis-removed)).
Plotly.js now offers a modebar button which sends the current chart to [Plotly Cloud](https://cloud.plotly.com) to generate a shareable link. Sharing the chart opens Plotly Cloud in a new tab, where you can copy the sharing link and adjust visibility settings. A Plotly Cloud account is required.

Comment on lines +164 to +169
The button's internal name (in `modeBarButtonsToAdd`, event handlers, etc.)
is `sendChartToCloud`. In v3 both `sendChartToCloud` and the alias
`sendDataToCloud` worked; in v4 the alias is removed — code that referred to
the button by the `sendDataToCloud` name needs to be updated.

### Opting out

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The button's internal name (in `modeBarButtonsToAdd`, event handlers, etc.)
is `sendChartToCloud`. In v3 both `sendChartToCloud` and the alias
`sendDataToCloud` worked; in v4 the alias is removed — code that referred to
the button by the `sendDataToCloud` name needs to be updated.
### Opting out

Comment on lines +214 to +216
MathJax v3 and v4 share the same rendering syntax, so Plotly no longer needs
version-specific code paths internally. Beyond dropping v2, the internal
`svg_text_utils.js` rewrite makes math rendering somewhat faster.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
MathJax v3 and v4 share the same rendering syntax, so Plotly no longer needs
version-specific code paths internally. Beyond dropping v2, the internal
`svg_text_utils.js` rewrite makes math rendering somewhat faster.
Math rendering should also be somewhat faster due to some internal changes.

Comment on lines +209 to +212
Plotly.js now supports **MathJax v3 and v4** for rendering LaTeX in labels,
titles, annotations, and hover text. Support for **MathJax v2 has been
dropped** — figures that rely on a v2 bundle will no longer render their math
expressions.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Plotly.js now supports **MathJax v3 and v4** for rendering LaTeX in labels,
titles, annotations, and hover text. Support for **MathJax v2 has been
dropped** — figures that rely on a v2 bundle will no longer render their math
expressions.
Plotly.js now supports **MathJax v3 and v4** for rendering LaTeX in labels,
titles, and annotations. Support for **MathJax v2 has been
dropped**.

```
- Already on v3? No action required — your math expressions continue to
render.
- On v4 already? No action required either.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- On v4 already? No action required either.

Comment on lines +220 to +221
- Loading MathJax v2 in the page? Upgrade to v3 or v4. The tex-svg build is
the one Plotly integrates with:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Loading MathJax v2 in the page? Upgrade to v3 or v4. The tex-svg build is
the one Plotly integrates with:
- Loading MathJax v2 in the page? Upgrade to v3 or v4. Plotly math rendering uses the tex-svg component (`tex-svg.js`):

<!-- or v4 (new) -->
<script src="https://cdn.jsdelivr.net/npm/mathjax@4/tex-svg.js"></script>
```
- Already on v3? No action required — your math expressions continue to

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Already on v3? No action required — your math expressions continue to
Note that the `?config=TeX-AMS-MML_SVG` URL suffix (used with MathJax v2) is no longer required starting with v3. Configuration is specified by linking directly to the desired JavaScript file, which is `tex-svg.js` in our case.
Already on v3? No action required — your math expressions continue to

Comment on lines +231 to +236

### Call sites unchanged

Plotly's public API for math (using `$…$` in text attributes) is unchanged.
The version negotiation happens inside Plotly at runtime — the correct
rendering path is chosen based on what MathJax exposes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Call sites unchanged
Plotly's public API for math (using `$…$` in text attributes) is unchanged.
The version negotiation happens inside Plotly at runtime — the correct
rendering path is chosen based on what MathJax exposes.


*Implemented in [#7861](https://github.com/plotly/plotly.js/pull/7861).*

Plotly.js now requires Node.js v22 or newer. The previous floor was v18, which

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Plotly.js now requires Node.js v22 or newer. The previous floor was v18, which
Plotly.js now requires Node.js v22 or newer. The previous minimum was v18, which

Comment on lines +1272 to +1274
Consumers who only load the pre-built browser bundle (`plotly.min.js` or a
partial bundle like `plotly-basic.min.js`) are unaffected — the bundle runs in
browsers, not Node.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Consumers who only load the pre-built browser bundle (`plotly.min.js` or a
partial bundle like `plotly-basic.min.js`) are unaffected — the bundle runs in
browsers, not Node.
Consumers who only load the pre-built browser bundle (`plotly.min.js` or a
partial bundle like `plotly-basic.min.js`) are unaffected.

Comment on lines +1177 to +1180
The legacy mapbox-gl-js–based traces and subplot type have been removed. The
MapLibre-based `map` family that has shadowed them since v2 is now the only
map implementation. For deeper background on the switch, see [Mapbox to
MapLibre migration](https://plotly.com/python/mapbox-to-maplibre/).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The legacy mapbox-gl-js–based traces and subplot type have been removed. The
MapLibre-based `map` family that has shadowed them since v2 is now the only
map implementation. For deeper background on the switch, see [Mapbox to
MapLibre migration](https://plotly.com/python/mapbox-to-maplibre/).
The legacy Mapbox–based traces and subplot type have been removed. The
equivalent MapLibre-based `map` traces, first released in plotly.js v3, are now the only
map implementation. For more information, see [Mapbox to
MapLibre migration](https://plotly.com/python/mapbox-to-maplibre/).

Comment on lines +1196 to +1198
The schema for `map` mirrors `mapbox` attribute-for-attribute (`domain`,
`style`, `center`, `zoom`, `bearing`, `pitch`, `bounds`, `layers[...]`), so
the migration is usually a rename.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The schema for `map` mirrors `mapbox` attribute-for-attribute (`domain`,
`style`, `center`, `zoom`, `bearing`, `pitch`, `bounds`, `layers[...]`), so
the migration is usually a rename.
The attributes for `map` traces are identical to those of `mapbox` traces, so
updating the trace name is all that's required to migrate.

Comment on lines +1224 to +1225
// mapboxAccessToken removed — MapLibre needs no token for the
// built-in styles

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// mapboxAccessToken removed — MapLibre needs no token for the
// built-in styles
// mapboxAccessToken no longer required

Comment on lines +1231 to +1233
Every built-in `style` name that worked on `mapbox` still works on `map`, but
the tiles backing them differ — visual output **will change** even when no
other figure changes:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Every built-in `style` name that worked on `mapbox` still works on `map`, but
the tiles backing them differ — visual output **will change** even when no
other figure changes:
Every built-in `style` value supported for `mapbox` traces works for `map` traces as well,
but the tile providers for some styles have changed. In those cases, the visual appearance
of the map will differ:

Comment on lines +1237 to +1242
| `basic`, `streets`, `outdoors` | mapbox.com vector tiles (required token) | Carto Voyager |
| `light` | mapbox.com light | Carto Positron |
| `dark` | mapbox.com dark | Carto Dark Matter |
| `satellite`, `satellite-streets` | mapbox.com satellite | ArcGIS World Imagery |
| `white-bg`, `open-street-map` | unchanged | unchanged |
| `carto-*`, `carto-*-nolabels` | n/a (already open) | unchanged |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| `basic`, `streets`, `outdoors` | mapbox.com vector tiles (required token) | Carto Voyager |
| `light` | mapbox.com light | Carto Positron |
| `dark` | mapbox.com dark | Carto Dark Matter |
| `satellite`, `satellite-streets` | mapbox.com satellite | ArcGIS World Imagery |
| `white-bg`, `open-street-map` | unchanged | unchanged |
| `carto-*`, `carto-*-nolabels` | n/a (already open) | unchanged |
| `'basic'`, `'streets'`, `'outdoors'` | mapbox.com 'basic' / 'streets' / 'outdoors' tiles | [Carto](https://carto.com/basemaps/) Voyager |
| `'light'` | mapbox.com 'light' tiles | [Carto](https://carto.com/basemaps/) Positron |
| `'dark'` | mapbox.com 'dark' tiles | [Carto](https://carto.com/basemaps/) Dark Matter |
| `'satellite'`, `satellite-streets` | mapbox.com satellite tiles | ArcGIS World Imagery |
| `'white-bg'` | solid white background | unchanged |
| `'open-street-map'` | [OpenStreetMap](https://www.openstreetmap.org) tiles | unchanged |
| `carto-*`, `carto-*-nolabels` | Corresponding [Carto](https://carto.com/basemaps/) tiles | unchanged |

Comment on lines +1244 to +1246
The three Stamen styles (`stamen-terrain`, `stamen-toner`,
`stamen-watercolor`) are gone — Stamen tiles now require a Stadia API key.
Users who need them can supply a custom `style` URL.

@emilykl emilykl Jul 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The three Stamen styles (`stamen-terrain`, `stamen-toner`,
`stamen-watercolor`) are gone — Stamen tiles now require a Stadia API key.
Users who need them can supply a custom `style` URL.
The three Stamen style values (`stamen-terrain`, `stamen-toner`, `stamen-watercolor`)
are no longer supported, since Stamen tiles require a Stadia API key. It is still possible to use
Stamen tiles, or any other third-party tiles, by passing a URL to a JSON style definition to `style`.

Comment on lines +1250 to +1253
Custom MapLibre / Mapbox-style JSON URLs continue to work. The
`mapbox://styles/mapbox/...` short form is no longer resolved, since it
requires authenticating to mapbox.com. Substitute the full URL of an
equivalent open style, or self-host the style JSON.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Custom MapLibre / Mapbox-style JSON URLs continue to work. The
`mapbox://styles/mapbox/...` short form is no longer resolved, since it
requires authenticating to mapbox.com. Substitute the full URL of an
equivalent open style, or self-host the style JSON.
Setting `style` to the URL of a MapLibre / Mapbox-style JSON style definition is still supported.
Custom Mapbox URLs of the form `mapbox://styles/mapbox/...` are no longer supported, since they
requires authenticating to mapbox.com.

Comment on lines +1152 to +1169
### `Plots.graphJson()` signature simplified

*See [#7829](https://github.com/plotly/plotly.js/pull/7829).*

`Plotly.Plots.graphJson()` previously accepted a `mode` argument that was
Chart Studio–specific. It has been dropped:

```js
// Before
const json = Plotly.Plots.graphJson(gd, opts, mode);

// After
const json = Plotly.Plots.graphJson(gd, opts);
```

If you were passing the third argument (typically `'keepdata'` or
`'keepall'`), remove it — that behavior was only meaningful when uploading
to Chart Studio.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function isn't even really part of the public API, so I'd suggest removing this section entirely. Any devs using this function can track down the reference in the changelog.

Suggested change
### `Plots.graphJson()` signature simplified
*See [#7829](https://github.com/plotly/plotly.js/pull/7829).*
`Plotly.Plots.graphJson()` previously accepted a `mode` argument that was
Chart Studio–specific. It has been dropped:
```js
// Before
const json = Plotly.Plots.graphJson(gd, opts, mode);
// After
const json = Plotly.Plots.graphJson(gd, opts);
```
If you were passing the third argument (typically `'keepdata'` or
`'keepall'`), remove it — that behavior was only meaningful when uploading
to Chart Studio.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add migration guide for v3 to v4

5 participants